{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# This cell is added by sphinx-gallery\n# It can be customized to whatever you like\n%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\nNoisy circuits\n==============\n\n.. meta::\n    :property=\"og:description\": Learn how to simulate noisy quantum circuits\n    :property=\"og:image\": https://pennylane.ai/qml/_images/N-Nisq.png\n\n.. related::\n\n    tutorial_noisy_circuit_optimization Optimizing noisy circuits with Cirq\n    pytorch_noise PyTorch and noisy devices\n\nIn this demonstration, you'll learn how to simulate noisy circuits using built-in functionality in\nPennyLane. We'll cover the basics of noisy channels and density matrices, then use example code to\nsimulate noisy circuits. PennyLane, the library for differentiable quantum computations, has\nunique features that enable us to compute gradients of noisy channels. We'll also explore how\nto employ channel gradients to optimize noise parameters in a circuit.\n\nWe're putting the N in NISQ.\n\n.. figure:: ../demonstrations/noisy_circuits/N-Nisq.png\n    :align: center\n    :width: 20%\n\n    ..\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Noisy operations\n----------------\nNoise is any unwanted transformation that corrupts the intended\noutput of a quantum computation. It can be separated into two categories.\n\n* **Coherent noise** is described by unitary operations that maintain the purity of the\n  output quantum state. A common source are systematic errors originating from\n  imperfectly-calibrated devices that do not exactly apply the desired gates, e.g., applying\n  a rotation by an angle $\\phi+\\epsilon$ instead of $\\phi$.\n\n* **Incoherent noise** is more problematic: it originates from a quantum computer\n  becoming entangled with the environment, resulting in mixed states --- probability\n  distributions over different pure states. Incoherent noise thus leads to outputs that are\n  always random, regardless of what basis we measure in.\n\nMixed states are described by `density matrices\n<https://en.wikipedia.org/wiki/Density_matrices>`__.\nThey provide a more general method of describing quantum states that elegantly\nencodes a distribution over pure states in a single mathematical object.\nMixed states are the most general description of a quantum state, of which pure\nstates are a special case.\n\nThe purpose of PennyLane's ``default.mixed`` device is to provide native\nsupport for mixed states and for simulating noisy computations. Let's use ``default.mixed`` to\nsimulate a simple circuit for preparing the\nBell state $|\\psi\\rangle=\\frac{1}{\\sqrt{2}}(|00\\rangle+|11\\rangle)$. We ask the QNode to\nreturn the expectation value of $Z_0\\otimes Z_1$:\n\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import pennylane as qml\nfrom pennylane import numpy as np\n\ndev = qml.device('default.mixed', wires=2)\n\n\n@qml.qnode(dev)\ndef circuit():\n    qml.Hadamard(wires=0)\n    qml.CNOT(wires=[0, 1])\n    return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))\n\n\nprint(f\"QNode output = {circuit():.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The device stores the output state as a density matrix. In this case, the density matrix is\nequal to $|\\psi\\rangle\\langle\\psi|$,\nwhere $|\\psi\\rangle=\\frac{1}{\\sqrt{2}}(|00\\rangle + |11\\rangle)$.\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(f\"Output state is = \\n{np.real(dev.state)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Incoherent noise is modelled by\nquantum channels. Mathematically, a quantum channel is a linear, completely positive,\nand trace-preserving (`CPTP\n<https://www.quantiki.org/wiki/channel-cp-map>`__) map. A convenient strategy for representing\nquantum channels is to employ `Kraus operators\n<https://en.wikipedia.org/wiki/Quantum_operation#Kraus_operators>`__\n$\\{K_i\\}$ satisfying the condition\n$\\sum_i K_{i}^{\\dagger} K_i = I$. For an initial state $\\rho$, the output\nstate after the action of a channel $\\Phi$ is:\n\n\\begin{align}\\Phi(\\rho) = \\sum_i K_i \\rho K_{i}^{\\dagger}.\\end{align}\n\nJust like pure states are special cases of mixed states, unitary\ntransformations are special cases of quantum channels. Unitary transformations are represented\nby a single Kraus operator,\nthe unitary $U$, and they transform a state as\n$U\\rho U^\\dagger$.\n\nMore generally, the action of a quantum channel can be interpreted as applying a\ntransformation corresponding to the Kraus operator $K_i$ with some associated\nprobability. More precisely, the channel applies the\ntransformation\n$\\frac{1}{p_i}K_i\\rho K_i^\\dagger$ with probability $p_i = \\text{Tr}[K_i \\rho K_{i}^{\n\\dagger}]$. Quantum\nchannels therefore represent a probability distribution over different possible\ntransformations on a quantum state. For\nexample, consider the bit flip channel. It describes a transformation that flips the state of\na qubit (applies an X gate) with probability $p$ and leaves it unchanged with probability\n$1-p$. Its Kraus operators are\n\n\\begin{align}K_0 &= \\sqrt{1-p}\\begin{pmatrix}1 & 0\\\\ 0 & 1\\end{pmatrix}, \\\\\n    K_1 &= \\sqrt{p}\\begin{pmatrix}0 & 1\\\\ 1 & 0\\end{pmatrix}.\\end{align}\n\nThis channel can be implemented in PennyLane using the :class:`qml.BitFlip <pennylane.BitFlip>`\noperation.\n\nLet's see what happens when we simulate this type of noise acting on\nboth qubits in the circuit. We'll evaluate the QNode for different bit flip probabilities.\n\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "@qml.qnode(dev)\ndef bitflip_circuit(p):\n    qml.Hadamard(wires=0)\n    qml.CNOT(wires=[0, 1])\n    qml.BitFlip(p, wires=0)\n    qml.BitFlip(p, wires=1)\n    return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))\n\n\nps = [0.001, 0.01, 0.1, 0.2]\nfor p in ps:\n    print(f\"QNode output for bit flip probability {p} is {bitflip_circuit(p):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The circuit behaves quite differently in the presence of noise! This will be familiar to anyone\nthat has run an algorithm on quantum hardware. It is also highlights why error\nmitigation and error correction are so important. We can use PennyLane to look under the hood and\nsee the output state of the circuit for the largest noise parameter\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(f\"Output state for bit flip probability {p} is \\n{np.real(dev.state)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Besides the bit flip channel, PennyLane supports several other noisy channels that are commonly\nused to describe experimental imperfections: :class:`~.pennylane.PhaseFlip`,\n:class:`~.pennylane.AmplitudeDamping`, :class:`~.pennylane.GeneralizedAmplitudeDamping`,\n:class:`~.pennylane.PhaseDamping`, and the :class:`~.pennylane.DepolarizingChannel`. You can also\nbuild your own custom channel using the operation :class:`~.pennylane.QubitChannel` by\nspecifying its Kraus operators, or even submit a `pull request\n<https://pennylane.readthedocs.io/en/stable/development/guide.html>`__ introducing a new channel.\n\nLet's take a look at another example. The depolarizing channel is a\ngeneralization of\nthe bit flip and phase flip channels, where each of the three possible Pauli errors can be\napplied to a single qubit. Its Kraus operators are given by\n\n\\begin{align}K_0 &= \\sqrt{1-p}\\begin{pmatrix}1 & 0\\\\ 0 & 1\\end{pmatrix}, \\\\\n    K_1 &= \\sqrt{p/3}\\begin{pmatrix}0 & 1\\\\ 1 & 0\\end{pmatrix}, \\\\\n    K_2 &= \\sqrt{p/3}\\begin{pmatrix}0 & -i\\\\ i & 0\\end{pmatrix}, \\\\\n    K_3 &= \\sqrt{p/3}\\begin{pmatrix}1 & 0\\\\ 0 & -1\\end{pmatrix}.\\end{align}\n\n\nA circuit modelling the effect of depolarizing noise in preparing a Bell state is implemented\nbelow.\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "@qml.qnode(dev)\ndef depolarizing_circuit(p):\n    qml.Hadamard(wires=0)\n    qml.CNOT(wires=[0, 1])\n    qml.DepolarizingChannel(p, wires=0)\n    qml.DepolarizingChannel(p, wires=1)\n    return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))\n\n\nps = [0.001, 0.01, 0.1, 0.2]\nfor p in ps:\n    print(f\"QNode output for depolarizing probability {p} is {depolarizing_circuit(p):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As before, the output deviates from the desired value as the amount of\nnoise increases.\nModelling the noise that occurs in real experiments requires careful consideration.\nPennyLane\noffers the flexibility to experiment with different combinations of noisy channels to either mimic\nthe performance of quantum algorithms when deployed on real devices, or to explore the effect\nof more general quantum transformations.\n\nChannel gradients\n-----------------\n\nThe ability to compute gradients of any operation is an essential ingredient of \n:doc:`quantum differentiable programming </glossary/quantum_differentiable_programming>`.\nIn PennyLane, it is possible to\ncompute gradients of noisy channels and optimize them inside variational circuits.\nPennyLane supports analytical\ngradients for channels whose Kraus operators are proportional to unitary\nmatrices [#johannes]_. In other cases, gradients are evaluated using finite differences.\n\nTo illustrate this property, we'll consider an elementary example. We aim to learn the noise\nparameters of a circuit in order to reproduce an observed expectation value. So suppose that we\nrun the circuit to prepare a Bell state\non a hardware device and observe that the expectation value of $Z_0\\otimes Z_1$ is\nnot equal to 1 (as would occur with an ideal device), but instead has the value 0.7781. In the\nexperiment, it is known that the\nmajor source of noise is amplitude damping, for example as a result of photon loss.\nAmplitude damping projects a state to $|0\\rangle$ with probability $p$ and\notherwise leaves it unchanged. It is\ndescribed by the Kraus operators\n\n\\begin{align}K_0 = \\begin{pmatrix}1 & 0\\\\ 0 & \\sqrt{1-p}\\end{pmatrix}, \\quad\n    K_1 = \\begin{pmatrix}0 & \\sqrt{p}\\\\ 0 & 0\\end{pmatrix}.\\end{align}\n\nWhat damping parameter ($p$) explains the experimental outcome? We can answer this question\nby optimizing the channel parameters to reproduce the experimental\nobservation! 💪 Since the parameter $p$ is a probability, we use a sigmoid function to\nensure that the trainable parameters give rise to a valid channel parameter, i.e., a number\nbetween 0 and 1.\n\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "ev = np.tensor([0.7781], requires_grad=False)  # observed expectation value\n\ndef sigmoid(x):\n    return 1/(1+np.exp(-x))\n\n@qml.qnode(dev)\ndef damping_circuit(x):\n    qml.Hadamard(wires=0)\n    qml.CNOT(wires=[0, 1])\n    qml.AmplitudeDamping(sigmoid(x), wires=0)  # p = sigmoid(x)\n    qml.AmplitudeDamping(sigmoid(x), wires=1)\n    return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We optimize the circuit with respect to a simple cost function that attains its minimum when\nthe output of the QNode is equal to the experimental value:\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def cost(x, target):\n    return (damping_circuit(x) - target[0])**2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All that remains is to optimize the parameter. We use a straightforward gradient descent\nmethod.\n\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "opt = qml.GradientDescentOptimizer(stepsize=10)\nsteps = 35\nx = np.tensor([0.0], requires_grad=True)\n\nfor i in range(steps):\n    (x, ev), cost_val = opt.step_and_cost(cost, x, ev)\n    if i % 5 == 0 or i == steps - 1:\n        print(f\"Step: {i}    Cost: {cost_val}\")\n\nprint(f\"QNode output after optimization = {damping_circuit(x):.4f}\")\nprint(f\"Experimental expectation value = {ev[0]}\")\nprint(f\"Optimized noise parameter p = {sigmoid(x.take(0)):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voilà! We've trained the noisy channel to reproduce the experimental observation. 😎\n\nDeveloping quantum algorithms that leverage the power of NISQ devices requires serious\nconsideration of the effects of noise. With PennyLane, you have access to tools that can\nhelp you design, simulate, and optimize noisy quantum circuits. We look forward to seeing what\nthe quantum community can achieve with them! 🚀 🎉 😸\n\nReferences\n----------\n\n.. [#johannes]\n\n    Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert, \"A variational toolbox for quantum\n    multi-parameter estimation.\" `arXiv:2006.06303 (2020) <https://arxiv.org/abs/2006.06303>`__.\n\n\n\n\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
